package com.novunda.platform.common.resolver;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.util.WebUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * 统一异常处理拦截器
 *
 * @author linfeng
 * @since 2016/9/28.
 */
public class GlobalExceptionResolverFilter implements Filter {

    private final static String EXCEPTION_ATTRIBUTE = "exception";
    private final static String statusCodeAttribute = "javax.servlet.error.status_code";
    private final static String messageAttribute = "javax.servlet.error.message";

    protected final Log logger = LogFactory.getLog(this.getClass());

    private Properties exceptionMappings;
    private Map<String, Integer> statusCodes = new HashMap<>();
    private Class<?>[] excludedExceptions;
    private String defaultErrorView = "error";
    private Integer defaultStatusCode = 200;

    private String exceptionAttribute = EXCEPTION_ATTRIBUTE;

    public void setExceptionAttribute(String exceptionAttribute) {
        this.exceptionAttribute = exceptionAttribute;
    }

    public void setExceptionMappings(Properties mappings) {
        this.exceptionMappings = mappings;
    }

    public void setExcludedExceptions(Class... excludedExceptions) {
        this.excludedExceptions = excludedExceptions;
    }

    public void setDefaultErrorView(String defaultErrorView) {
        this.defaultErrorView = defaultErrorView;
    }

    public void setStatusCodes(Properties statusCodes) {
        Enumeration enumeration = statusCodes.propertyNames();

        while (enumeration.hasMoreElements()) {
            String viewName = (String) enumeration.nextElement();
            Integer statusCode = new Integer(statusCodes.getProperty(viewName));
            this.statusCodes.put(viewName, statusCode);
        }

    }

    public void addStatusCode(String viewName, int statusCode) {
        this.statusCodes.put(viewName, statusCode);
    }

    public Map<String, Integer> getStatusCodesAsMap() {
        return Collections.unmodifiableMap(this.statusCodes);
    }

    public void setDefaultStatusCode(int defaultStatusCode) {
        this.defaultStatusCode = defaultStatusCode;
    }

    //==================================================================================================================

    protected int getDepth(String exceptionMapping, Exception ex) {

        return this.getDepth(exceptionMapping, ex.getCause() == null ? ex.getClass() : ex.getCause().getClass(), 0);
    }

    private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
        return exceptionClass.getName().contains(exceptionMapping) ? depth : (exceptionClass == Throwable.class ? -1 : this.getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1));
    }

    protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
        String viewName = null;
        String dominantMapping = null;
        int deepest = 2147483647;
        Enumeration names = exceptionMappings.propertyNames();

        while (true) {
            String exceptionMapping;
            int depth;
            do {
                do {
                    if (!names.hasMoreElements()) {
                        if (viewName != null && this.logger.isDebugEnabled()) {
                            this.logger.debug("Resolving to view \'" + viewName + "\' for exception of type [" + ex.getClass().getName() + "], based on exception mapping [" + dominantMapping + "]");
                        }

                        return viewName;
                    }

                    exceptionMapping = (String) names.nextElement();
                    depth = this.getDepth(exceptionMapping, ex);
                } while (depth < 0);
            }
            while (depth >= deepest && (depth != deepest || dominantMapping == null || exceptionMapping.length() <= dominantMapping.length()));

            deepest = depth;
            dominantMapping = exceptionMapping;
            viewName = exceptionMappings.getProperty(exceptionMapping);
        }
    }

    protected String determineViewName(Exception ex) {
        String viewName = null;
        if (this.excludedExceptions != null) {
            Class[] var4 = this.excludedExceptions;

            for (Class excludedEx : var4) {
                if (excludedEx.equals(ex.getClass())) {
                    return null;
                }
            }
        }

        if (this.exceptionMappings != null) {
            viewName = this.findMatchingViewName(this.exceptionMappings, ex);
        }

        if (viewName == null && this.defaultErrorView != null) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving to default view \'" + this.defaultErrorView + "\' for exception of type [" + ex.getClass().getName() + "]");
            }

            viewName = this.defaultErrorView;
        }

        return viewName;
    }

    protected Integer determineStatusCode(String viewName) {
        return this.statusCodes.containsKey(viewName) ? this.statusCodes.get(viewName) : this.defaultStatusCode;
    }

    protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, Integer statusCode) {
        if (!WebUtils.isIncludeRequest(request)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Applying HTTP status code " + statusCode);
            }

            response.setStatus(statusCode);
            request.setAttribute(statusCodeAttribute, statusCode);
        }

    }

    //==================================================================================================================

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //捕获你抛出的业务异常
        try {
            chain.doFilter(servletRequest, servletResponse);
        } catch (Exception ex) {

            logger.error(ex.getMessage(), ex);

            String viewName = this.determineViewName(ex);
            if (viewName != null) {

                Integer statusCode = this.determineStatusCode(viewName);
                if (statusCode != null) {
                    this.applyStatusCodeIfPossible(request, response, statusCode);
                }
                servletRequest.setAttribute(messageAttribute, ex.getMessage());
                servletRequest.setAttribute(exceptionAttribute, ex);

                try {
                    servletRequest.getRequestDispatcher(viewName).forward(request, response);//跳转到信息提示页面！！
                } catch (Exception e) {
                    logger.error(ex.getMessage(), ex);
                }

            }


        }

    }

    @Override
    public void destroy() {

    }


}
